import {getRect, offset} from "./utils/dom";
import {EventHandler, EventNames, MoveBase, Options, RectBoundInfo} from "./types";
import {warn} from "./utils/debug";
import {CoreMixin} from "./core";
import {DEFAULT_OPTIONS, DIRECTION} from "./consts";
import {eventMixin} from "./event";
import {ease} from "./utils/ease";

export class BScroll extends eventMixin<EventNames> {
  readonly wrapper!: HTMLElement;
  readonly scroller!: HTMLElement; // 滚动的元素
  readonly scrollerStyle!: CSSStyleDeclaration;
  readonly core!: CoreMixin;
  options!: Options;
  scale = 1;
  hasVerticalScroll = false;
  base: MoveBase = {
    moving: false,
    inTransition: false,
    x: 0,
    startX: 0,
    pointX: 0,
    absStartX: 0,
    directionX: DIRECTION.static,
    y: 0,
    startY: 0,
    pointY: 0,
    absStartY: 0,
    directionY: DIRECTION.static,
    startTime: 0,
    endTime: 0,
  }
  rectInfo: RectBoundInfo = {
    wrapperHeight: 0,
    scrollerHeight: 0,
    minScrollY: 0,
    maxScrollY: 0,
    relativeY: 0,
    wrapperOffset: { left: 0, top: 0 },
  }

  startEventHandler!: EventHandler<TouchEvent>;
  moveEventHandler!: EventHandler<TouchEvent>;
  endEventHandler!: EventHandler<TouchEvent>;
  transitionEndEventHandler!: EventHandler<TransitionEvent>;

  constructor(el: string | HTMLElement, options?: Partial<Options>) {
    super();
    const wrapper = typeof el === 'string' ? document.querySelector<HTMLElement>(el) : el;
    if (wrapper) {
      const scroller = wrapper.children[0] as HTMLElement;
      if (scroller) {
        this.wrapper = wrapper;
        this.scroller = scroller;
        this.scrollerStyle = this.scroller.style;
        this.core = new CoreMixin(this);
        this.handleOptions(options);
        this.init();
        // const height = getComputedStyle(this.wrapper).getPropertyValue('height'); // 可读取maxHeight
      } else {
        warn('The wrapper need at least one child element to be scroller.')
      }
    } else {
      warn('Can not resolve the wrapper DOM.')
    }
  }
  protected init() {
    this.addEvents();
    this.refresh();
    this.core.scrollTo(this.options.startX, this.options.startY);
  }

  protected handleOptions (options?: Partial<Options>) {
    if (options) {
      this.options = Object.assign({}, DEFAULT_OPTIONS, options);
      if (this.options.tap === true) {
        this.options.tap = 'tap';
      }
    } else {
      this.options = DEFAULT_OPTIONS;
    }
  }

  private refresh() {
    const isWrapperStatic = getComputedStyle(this.wrapper, null).position === 'static'
    const wrapperRect = getRect(this.wrapper);
    this.rectInfo.wrapperHeight = wrapperRect.height;

    const scrollerRect = getRect(this.scroller);
    this.rectInfo.scrollerHeight = Math.round(scrollerRect.height * this.scale);

    this.rectInfo.relativeY = scrollerRect.top

    if (isWrapperStatic) {
      // scrollerRect.top - wrapperRect.top, 比如最外层wrap有padding-top, maxScrollY就应该减掉relativeY
      this.rectInfo.relativeY -= wrapperRect.top
    }

    this.rectInfo.minScrollY = 0;
    this.rectInfo.maxScrollY = this.rectInfo.wrapperHeight - this.rectInfo.scrollerHeight;
    // console.log('relativeY', this.rectInfo.relativeY);
    if (this.rectInfo.maxScrollY < 0) {
      this.rectInfo.maxScrollY -= this.rectInfo.relativeY;
      this.rectInfo.minScrollY = -this.rectInfo.relativeY;
    }

    // this.options.scrollY &&
    this.hasVerticalScroll = this.rectInfo.maxScrollY < this.rectInfo.minScrollY;

    if (!this.hasVerticalScroll) {
      this.rectInfo.maxScrollY = this.rectInfo.minScrollY;
      this.rectInfo.scrollerHeight = this.rectInfo.wrapperHeight;
    }

    this.base.endTime = 0;
    this.rectInfo.wrapperOffset = offset(this.wrapper);
    this.emit('refresh');
  }
  private addEvents() {
    const target = this.options.bindToWrapper ? this.wrapper : window;
    // bind会返回一个新fn, 不保存的话无法remove
    this.startEventHandler = this.core.handleStart.bind(this.core);
    this.moveEventHandler = this.core.handleMove.bind(this.core);
    this.endEventHandler = this.core.handleEnd.bind(this.core);
    this.transitionEndEventHandler = this.core.handleTransitionEnd.bind(this.core);
    // 外部的click都会被touch事件阻止, 包括window
    this.wrapper.addEventListener('touchstart', this.startEventHandler, { passive: false });

    // @ts-ignore
    target.addEventListener('touchmove', this.moveEventHandler, { passive: false });
    // @ts-ignore
    target.addEventListener('touchend', this.endEventHandler);
    // target.addEventListener('touchcancel', this.core.handleEnd.bind(this.core));
    this.scroller.addEventListener('transitionend', this.transitionEndEventHandler);
  }
  private removeEvents() {
    const target = this.options.bindToWrapper ? this.wrapper : window;
    this.wrapper.removeEventListener('touchstart', this.startEventHandler);
    // @ts-ignore
    target.removeEventListener('touchmove', this.moveEventHandler);
    // @ts-ignore
    target.removeEventListener('touchend', this.endEventHandler);
    // target.removeEventListener('touchcancel', this.core.handleEnd.bind(this.core));
    this.scroller.removeEventListener('transitionend', this.transitionEndEventHandler);
  }
  destroy() {
    this.emit('destroy');
    this.removeEvents();
    this.events.clear();
  }
  scrollToElement(el: HTMLElement | string, time: number, offsetX: number | 'center' = 0, offsetY: number | 'center' = 0, easing = ease.bounce) {
    const targetEl = typeof el === 'string' ? this.scroller.querySelector(el) as HTMLElement : el;
    // console.log('targetEl', targetEl);
    if (this.scroller.contains(targetEl)) {
      // debugger;
      let { left, top } = offset(targetEl);
      left -= this.rectInfo.wrapperOffset.left;
      top -= this.rectInfo.wrapperOffset.top;

      // if offsetX/Y are true, 把element定位到wrapper中间
      if (offsetX === 'center') {
        offsetX = Math.round(targetEl.offsetWidth / 2 - this.wrapper.offsetWidth / 2);
      }
      if (offsetY === 'center') {
        offsetY = Math.round(targetEl.offsetHeight / 2 - this.wrapper.offsetHeight / 2);
      }

      left -= offsetX || 0;
      top -= offsetY || 0;
      // left = left > this.rectInfo.minScrollX ? this.minScrollX : pos.left < this.maxScrollX ? this.maxScrollX : pos.left
      top = top > this.rectInfo.minScrollY ? this.rectInfo.minScrollY : top < this.rectInfo.maxScrollY ? this.rectInfo.maxScrollY : top;
      this.core.scrollTo(left, top, time, easing);
    }
  }
}
